/**
 * Copyright(c) 2014 Xunton Solutions
 *
 * History:
 *   15-9-28 下午4:50 Created by dwzhang
 */
package com.jade.bss.weixin.api.yaozb;

import java.io.File;
import java.io.InputStream;
import java.util.*;
import javax.inject.Named;

import com.jade.bss.weixin.WeixinException;
import com.jade.bss.weixin.api.protocol.json.JsonApi;
import com.jade.bss.weixin.api.yaozb.protocol.*;
import com.xunton.bss.weixin.api.Response;
import com.xunton.bss.weixin.api.yaozb.pdu.BeaconInfo;

/**
 * 摇周边接口。
 *
 * @author <a href="mailto:dwzhang@xunton.com">dwzhang</a> 
 * @version 1.0 15-9-28 下午4:50
 */
@Named ("bss_weixinYaozbApi")
public class YaozbApi
        extends JsonApi
{
    private final static String URL_APPLY_BEACON = "https://api.weixin.qq.com/shakearound/device/applyid?access_token=";
    private final static String URL_QUERY_APPLY = "https://api.weixin.qq.com/shakearound/device/applystatus?access_token=";
    private final static String URL_UPDATE_BEACON = "https://api.weixin.qq.com/shakearound/device/update?access_token=";
    private final static String URL_BIND_LOCATION = "https://api.weixin.qq.com/shakearound/device/bindlocation?access_token=";
    private final static String URL_SEARCH_BEACON = "https://api.weixin.qq.com/shakearound/device/search?access_token=";
    private final static String URL_ADD_PAGE = "https://api.weixin.qq.com/shakearound/page/add?access_token=";
    private final static String URL_UPDATE_PAGE = "https://api.weixin.qq.com/shakearound/page/update?access_token=";
    private final static String URL_SEARCH_PAGE = "https://api.weixin.qq.com/shakearound/page/search?access_token=";
    private final static String URL_REMOVE_PAGE = "https://api.weixin.qq.com/shakearound/page/delete?access_token=";
    private final static String URL_UPLOAD_PIC = "https://api.weixin.qq.com/shakearound/material/add?access_token=";
    private final static String URL_BIND_PAGE = "https://api.weixin.qq.com/shakearound/device/bindpage?access_token=";
    private final static String URL_RELATION_SEARCH = "https://api.weixin.qq.com/shakearound/relation/search?access_token=";
    private final static String URL_USER_SHAKE_INFO = "https://api.weixin.qq.com/shakearound/user/getshakeinfo?access_token=";
    private final static String URL_STAT_DEVICE_PAGE = "https://api.weixin.qq.com/shakearound/statistics/device?access_token=";
    private final static String URL_STAT_URL_PAGE = "https://api.weixin.qq.com/shakearound/statistics/page?access_token=";

    /**
     * 申请配置设备所需的UUID、Major、Minor。申请成功后返回批次ID，可用返回的批次ID通过“查询设备ID申请状态”接口查询
     * 目前申请的审核状态。
     * 若单次申请的设备ID数量小于500个，系统会进行快速审核；若单次申请的设备ID数量大于等 500个 ，会在三个工作日内完成审核。
     * 如果已审核通过，可用返回的批次ID通过“查询设备列表”接口拉取本次申请的设备ID。 通过接口申请的设备ID，
     * 需先配置页面，若未配置页面，则摇不出页面信息。
     * 一个公众账号最多可申请99999个设备ID，如需申请的设备ID数超过最大限额，请邮件至zhoubian@tencent.com，邮件格式如下：
     * 标题：申请提升设备ID额度
     * 内容：
     * 1、公众账号名称及appid（wx开头的字符串，在mp平台可查看）
     * 2、用途
     * 3、预估需要多少设备ID
     *
     * @param token access token
     * @param quantity  申请的设备ID的数量，单次新增设备超过500个，需走人工审核流程
     * @param reason 申请理由，不超过100个汉字或200个英文字母
     * @param comment 备注，不超过15个汉字或30个英文字母
     * @param poiId  设备关联的门店ID，关联门店后，在门店1KM的范围内有优先摇出信息的机会。
     * @return 设备申请的状态信息
     * @throws WeixinException 处理异常
     */
    public ApplyData applyBeacon(String token, int quantity, String reason, String comment, String poiId)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("quantity", quantity);
        params.put("apply_reason", reason);
        params.put("comment", comment);
        params.put("poi_id", poiId);
        ApplyResponse response = post(URL_APPLY_BEACON + token, params, ApplyResponse.class);
        return response.getData();
    }

    /**
     * 询设备ID申请的审核状态。若单次申请的设备ID数量小于等于500个，系统会进行快速审核；
     * 若单次申请的设备ID数量大于500个，则在三个工作日内完成审核。
     *
     * @param token 调用接口凭证
     * @param applyId 批次ID，申请设备ID时所返回的批次ID
     * @return 申请的状态信息。
     * @throws WeixinException 处理异常
     */
    public QueryApplyData queryApply(String token, long applyId)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("apply_id", applyId);
        QueryApplyResponse response = post(URL_QUERY_APPLY + token, params, QueryApplyResponse.class);
        return response.getData();
    }

    /**
     * 编辑设备的备注信息。可用设备ID或完整的UUID、Major、Minor指定设备，二者选其一。
     *
     * @param token 调用接口凭证
     * @param deviceId 指定的设备ID
     * @param comment 设备的备注信息，不超过15个汉字或30个英文字母。
     * @throws WeixinException 处理异常
     */
    public void updateBeacon(String token, long deviceId, String comment)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setDeviceId(deviceId);
        params.put("device_identifier", beacon);
        params.put("comment", comment);
        post(URL_UPDATE_BEACON + token, params, Response.class);
    }

    public void updateBeacon(String token, String uuid, int major, int minor, String comment)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setUuid(uuid);
        beacon.setMajor(major);
        beacon.setMinor(minor);
        params.put("device_identifier", beacon);
        params.put("comment", comment);
        post(URL_UPDATE_BEACON + token, params, Response.class);
    }

    /**
     * 修改设备关联的门店ID。可用设备ID或完整的UUID、Major、Minor指定设备，二者选其一。
     *
     * @param token 调用接口凭证
     * @param deviceId 设备编号，若填了UUID、major、minor，则可不填设备编号，若二者都填，则以设备编号为优先
     * @param poiId  设备关联的门店ID，关联门店后，在门店1KM的范围内有优先摇出信息的机会。当值为0时，将清除设备已关联的门店ID。
     * @throws WeixinException 处理异常
     */
    public void bindLocation(String token, long deviceId, long poiId)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setDeviceId(deviceId);
        params.put("device_identifier", beacon);
        params.put("poi_id", poiId);
        post(URL_BIND_LOCATION + token, params, Response.class);
    }

    public void bindLocation(String token, String uuid, int major, int minor, long poiId)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setUuid(uuid);
        beacon.setMajor(major);
        beacon.setMinor(minor);
        params.put("device_identifier", beacon);
        params.put("poi_id", poiId);
        post(URL_BIND_LOCATION + token, params, Response.class);
    }

    /**
     * 根据申请批次查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。
     *
     * @param token 调用接口凭证
     * @param applyId 批次ID，申请设备ID时所返回的批次ID；当type为3时，此项为必填
     * @param offset 设备列表的起始索引值
     * @param length 待查询的设备数量，不能超过50个
     * @return 设备信息
     * @throws WeixinException 处理异常
     */
    public SearchBeaconData searchBeacon(String token, long applyId, int offset, int length)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 3);
        params.put("apply_id", applyId);
        params.put("begin", offset);
        params.put("count", length);
        SearchBeaconResponse response = post(URL_SEARCH_BEACON + token, params, SearchBeaconResponse.class);
        return response.getData();
    }

    /**
     * 查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。
     *
     * @param token 调用接口凭证
     * @param offset 设备列表的起始索引值
     * @param length 待查询的设备数量，不能超过50个
     * @return 设备信息
     * @throws WeixinException 处理异常
     */
    public SearchBeaconData searchBeacon(String token, int offset, int length)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 2);
        params.put("begin", offset);
        params.put("count", length);
        SearchBeaconResponse response = post(URL_SEARCH_BEACON + token, params, SearchBeaconResponse.class);
        return response.getData();
    }

    /**
     * 根据指定设备标识查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。
     *
     * @param token 调用接口凭证
     * @param deviceIds 设备编号
     * @return 设备信息
     * @throws WeixinException 处理异常
     */
    public SearchBeaconData searchBeacon(String token, long... deviceIds)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 1);
        List<BeaconInfo> infos = new ArrayList<BeaconInfo>();
        for (long deviceId : deviceIds) {
            BeaconInfo info = new BeaconInfo();
            info.setDeviceId(deviceId);
            infos.add(info);
        }
        params.put("device_identifiers", infos);
        SearchBeaconResponse response = post(URL_SEARCH_BEACON + token, params, SearchBeaconResponse.class);
        return response.getData();
    }

    /**
     * 新增摇一摇出来的页面信息，包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
     * 其中，图片必须为用素材管理接口上传至微信侧服务器后返回的链接。
     *
     * @param token 调用接口凭证
     * @param page 页面信息。
     * @return 界面标识。
     * @throws WeixinException 处理异常
     */
    public PageData addPage(String token, PageInfo page)
            throws WeixinException
    {
        AddPageResponse response = post(URL_ADD_PAGE + token, page, AddPageResponse.class);
        return response.getPageData();
    }

    /**
     * 编辑摇一摇出来的页面信息，包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
     *
     * @param token 调用接口凭证
     * @param page 页面信息。
     * @throws WeixinException 处理异常
     */
    public void updatePage(String token, PageInfo page)
            throws WeixinException
    {
        post(URL_UPDATE_PAGE + token, page, Response.class);
    }

    /**
     * 批量查询已有的页面，包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
     *
     * @param token 调用接口凭证
     * @param offset 页面列表的起始索引值；当type为2时，此项为必填
     * @param length 待查询的页面数量，不能超过50个；当type为2时，此项为必填
     * @return 页面数据信息。
     * @throws WeixinException 处理异常
     */
    public SearchPageData searchPage(String token, int offset, int length)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 2);
        params.put("begin", offset);
        params.put("count", length);
        SearchPageResponse response = post(URL_SEARCH_PAGE + token, params, SearchPageResponse.class);
        return response.getSearchPageData();
    }

    /**
     * 批量查询已有的页面，包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
     *
     * @param token 调用接口凭证
     * @param pageIds 指定页面的id列表
     * @return 页面数据信息。
     * @throws WeixinException 处理异常
     */
    public SearchPageData searchPage(String token, long... pageIds)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 1);
        List<Long> pages = new ArrayList<Long>();
        for (long pageId : pageIds) {
            pages.add(pageId);
        }
        params.put("page_ids", pages);
        SearchPageResponse response = post(URL_SEARCH_PAGE + token, params, SearchPageResponse.class);
        return response.getSearchPageData();
    }

    /**
     * 删除已有的页面，包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
     * 只有页面与设备没有关联关系时，才可被删除.
     *
     * @param token 调用接口凭证
     * @param id 指定页面的id
     * @throws Exception
     */
    public void deletePage(String token, long id)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("page_id", id);
        post(URL_REMOVE_PAGE + token, params, Response.class);
    }

    /**
     * 上传在摇一摇功能中需使用到的图片素材，素材保存在微信侧服务器上。图片格式限定为：jpg,jpeg,png,gif。
     * 若图片为在摇一摇页面展示的图片，则其素材为icon类型的图片，图片大小建议120px*120 px，限制不超过200 px *200 px，图片需为正方形。
     * 若图片为申请开通摇一摇周边功能需要上传的资质文件图片，则其素材为license类型的图片，图片的文件大小不超过2MB，尺寸不限，形状不限。
     *
     * @param token 调用接口凭证
     * @param file icon文件
     * @return 图片数据
     * @throws WeixinException 处理异常
     */
    public IconData uploadPageIcon(String token, File file)
            throws WeixinException
    {
        UploadIconResponse response = postFile(URL_UPLOAD_PIC + token, "media", file, UploadIconResponse.class);
        return response.getIconData();
    }

    public IconData uploadPageIcon(String token, InputStream in, String mimeType, String fileName)
            throws WeixinException
    {
        UploadIconResponse response = postFile(URL_UPLOAD_PIC + token, "media", in, mimeType, fileName,
                UploadIconResponse.class);
        return response.getIconData();
    }

    /**
     * 配置设备与页面的关联关系。配置时传入该设备需要关联的页面的id列表（该设备原有的关联关系将被直接清除）；
     * 页面的id列表允许为空，当页面的id列表为空时则会清除该设备的所有关联关系。配置完成后，在此设备的信号范围内，
     * 即可摇出关联的页面信息
     * 在申请设备ID后，可直接使用接口直接配置页面。
     * 若设备配置多个页面，则随机出现页面信息。一个设备最多可配置30个关联页面。
     *
     * @param token 调用接口凭证
     * @param deviceId 设备编号，若填了UUID、major、minor，则可不填设备编号，若二者都填，则以设备编号为优先
     * @param pageIds 待关联的页面列表
     * @throws Exception
     */
    public void bindPage(String token, long deviceId, long[] pageIds)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setDeviceId(deviceId);
        params.put("device_identifier", beacon);
        params.put("page_ids", pageIds);
        post(URL_BIND_PAGE + token, params, Response.class);
    }

    public void bindPage(String token, String uuid, int major, int minor, long[] pageIds)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        BeaconInfo beacon = new BeaconInfo();
        beacon.setUuid(uuid);
        beacon.setMajor(major);
        beacon.setMinor(minor);
        params.put("device_identifier", beacon);
        params.put("page_ids", pageIds);
        post(URL_BIND_PAGE + token, params, Response.class);
    }

    /**
     * 查询设备与页面的关联关系。
     * 指定页面ID分页查询该页面所关联的所有的设备信息
     *
     * @param token 调用接口凭证
     * @param pageId 指定的页面id；当type为2时，此项为必填
     * @param offset 关联关系列表的起始索引值；当type为2时，此项为必填
     * @param length 待查询的关联关系数量，不能超过50个；当type为2时，此项为必填
     * @return 设备与页面的关联关系。
     * @throws WeixinException 处理异常
     */
    public RelationData searchRelation(String token, long pageId, int offset, int length)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 1);
        params.put("begin", offset);
        params.put("count", length);
        SearchRelationResponse response = post(URL_RELATION_SEARCH + token, params, SearchRelationResponse.class);
        return response.getData();
    }

    /**
     * 根据设备ID或完整的UUID、Major、Minor查询该设备所关联的所有页面信息。
     *
     * @param token 调用接口凭证
     * @param deviceId 设备标识
     * @return 设备与页面的关联关系。
     * @throws WeixinException 处理异常
     */
    public RelationData searchRelationByDevice(String token, long deviceId)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 1);
        BeaconInfo beacon = new BeaconInfo();
        beacon.setDeviceId(deviceId);
        params.put("device_identifier", beacon);
        SearchRelationResponse response = post(URL_RELATION_SEARCH + token, params, SearchRelationResponse.class);
        return response.getData();

    }

    public RelationData searchRelationByDevice(String token, String uuid, int major, int minor)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("type", 1);
        BeaconInfo beacon = new BeaconInfo();
        beacon.setUuid(uuid);
        beacon.setMajor(major);
        beacon.setMinor(minor);
        params.put("device_identifier", beacon);
        SearchRelationResponse response = post(URL_RELATION_SEARCH + token, params, SearchRelationResponse.class);
        return response.getData();
    }

    /**
     * 获取设备信息，包括UUID、major、minor，以及距离、openID等信息。
     *
     * @param ticket 摇周边业务的ticket，可在摇到的URL中得到，ticket生效时间为30分钟，每一次摇都会重新生成新的ticket
     * @return 设备信息
     * @throws WeixinException 处理异常
     */
    public UserInfoData getShakeInfo(String token, String ticket, boolean needPoi)
            throws WeixinException
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("ticket", ticket);
        params.put("need_poi", needPoi ? 1 : 0);
        UserInfoResponse response = post(URL_USER_SHAKE_INFO + token, params, UserInfoResponse.class);
        return response.getUserInfo();
    }

    /**
     *  查询单个设备进行摇周边操作的人数、次数，点击摇周边消息的人数、次数；查询的最长时间跨度为30天。只能查询最近90天的数据。
     *  此接口无法获取当天的数据，最早只能获取前一天的数据。由于系统在凌晨处理前一天的数据，太早调用此接口
     *  可能获取不到数据，建议在早上8：00之后调用此接口。
     *
     * @param token 调用接口凭证
     * @param deviceId 指定页面的设备ID
     * @param start 起始日期时间戳，最长时间跨度为30天，单位为秒
     * @param end 结束日期时间戳，最长时间跨度为30天，单位为秒
     * @return 统计数据
     * @throws WeixinException 处理异常
     */
    public List<StatItem> statBeacon(String token, long deviceId, Date start, Date end)
            throws WeixinException
    {
        if (end == null) {
            end = new Date();
        }
        if (start == null) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DATE, -10);
            start = calendar.getTime();
        }
        BeaconInfo info = new BeaconInfo();
        info.setDeviceId(deviceId);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("begin_date", start.getTime() / 1000);
        params.put("end_date", end.getTime() / 1000);
        params.put("device_identifier", info);
        StatResponse response = post(URL_STAT_DEVICE_PAGE + token, params, StatResponse.class);
        return response.getItems();


    }

    /**
     * 查询单个页面通过摇周边摇出来的人数、次数，点击摇周边页面的人数、次数；查询的最长时间跨度为30天。只能查询最近90天的数据。
     * 此接口无法获取当天的数据，最早只能获取前一天的数据。由于系统在凌晨处理前一天的数据，
     * 太早调用此接口可能获取不到数据，建议在早上8：00之后调用此接口。
     *
     * @param token 调用接口凭证
     * @param pageId 指定页面的设备ID
     * @param start 起始日期时间戳，最长时间跨度为30天，单位为秒
     * @param end 结束日期时间戳，最长时间跨度为30天，单位为秒
     * @return 统计信息
     * @throws WeixinException 处理异常
     */
    public List<StatItem> statPage(String token, long pageId, Date start, Date end)
            throws WeixinException
    {
        if (end == null) {
            end = new Date();
        }
        if (start == null) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DATE, -29);
            start = calendar.getTime();
        }
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("begin_date", start.getTime() / 1000);
        params.put("end_date", end.getTime() / 1000);
        params.put("page_id", pageId);
        StatResponse response = post(URL_STAT_URL_PAGE + token, params, StatResponse.class);
        return response.getItems();
    }
}
